//+------------------------------------------------------------------+
//|                                                   RadioGroup.mqh |
//|                             Copyright 2000-2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "WndClient.h"                          // 客户区窗口基类（提供控件容器与滚动能力）
#include "RadioButton.h"                       // 单选按钮类（单选组的子项）
#include <Arrays/ArrayString.h>                 // 字符串数组（存储单选项文本）
#include <Arrays/ArrayLong.h>                   // 长整数数组（存储单选项关联值）

//+------------------------------------------------------------------+
//| CRadioGroup 类：MQL5 单选按钮组控件类                              |
//| 核心能力：管理一组互斥的单选按钮，支持滚动显示、数据增删、状态持久化， |
//|           确保同一时间仅一个单选按钮被选中，适用于多选项互斥场景       |
//| 适用场景：批量互斥选项（如周期选择、交易类型选择、参数模式选择等）     |
//+------------------------------------------------------------------+
class CRadioGroup : public CWndClient
  {
private:
   // 子控件与布局参数（私有，仅内部管理）
   CRadioButton      m_rows[];              // 单选按钮数组（每个元素对应一个单选项）
   int               m_offset;              // 可见区域起始项索引（滚动偏移量，控制显示范围）
   int               m_total_view;          // 可见项数量（当前可显示的最大单选按钮数）
   int               m_item_height;         // 单个单选按钮高度（像素，固定值）

   // 数据存储（私有，确保数据一致性）
   CArrayString      m_strings;             // 单选项文本数组（每个单选按钮的显示文本）
   CArrayLong        m_values;              // 单选项关联值数组（每个单选按钮对应的业务值）
   int               m_current;             // 当前选中项索引（CONTROLS_INVALID_INDEX 表示无选中）

public:
   //----------------------------------------------------------------
   // 构造与析构函数
   //----------------------------------------------------------------
   /**
    * @brief 构造函数：初始化单选按钮组默认状态
    * @details 初始化滚动偏移量为 0、可见项数量为 0、单个项高度为系统默认（CONTROLS_LIST_ITEM_HEIGHT），
    *          当前选中项为无效值，数据数组为空
    */
                     CRadioGroup(void);

   /**
    * @brief 析构函数：清理单选按钮组资源
    * @details 释放子控件数组、数据数组，依赖父类 CWndClient 析构逻辑清理容器与滚动条资源
    */
                    ~CRadioGroup(void);

   //----------------------------------------------------------------
   // 控件创建与销毁：初始化单选组并添加到图表
   //----------------------------------------------------------------
   /**
    * @brief 在指定图表位置创建单选按钮组控件
    * @param chart [in] 目标图表ID（0 表示当前活跃图表）
    * @param name [in] 控件唯一标识名（图表内不可重复）
    * @param subwin [in] 绑定的子窗口编号（0 = 主窗口，≥1 = 指标子窗口）
    * @param x1 [in] 左上角X坐标（像素，相对于图表窗口）
    * @param y1 [in] 左上角Y坐标（像素）
    * @param x2 [in] 右下角X坐标（像素）
    * @param y2 [in] 右下角Y坐标（像素）
    * @return bool - 创建结果：true=成功（父类初始化+子单选按钮创建完成），false=失败
    * @note 1. 自动计算可见项数量（基于控件高度和单个项高度）；
    *       2. 创建指定数量的单选按钮并初始化样式，默认隐藏（添加数据后显示）
    */
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);

   /**
    * @brief 销毁单选按钮组控件
    * @param reason [in] 销毁原因（默认 0，预留参数）
    * @details 清理子控件数组、数据数组，调用父类销毁方法释放容器与滚动条资源
    */
   virtual void      Destroy(const int reason=0);

   //----------------------------------------------------------------
   // 事件处理：响应图表与用户交互事件
   //----------------------------------------------------------------
   /**
    * @brief 处理单选按钮组相关事件（如单选项状态变更、滚动等）
    * @param id [in] 事件ID（如 ON_CHANGE、ON_SCROLL 等）
    * @param lparam [in] 长整数参数（事件相关数据，如子项索引）
    * @param dparam [in] 双精度参数（事件相关数据）
    * @param sparam [in] 字符串参数（通常为控件名称，用于识别事件来源）
    * @return bool - 处理结果：true=事件已处理，false=未处理（交由父类处理）
    */
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

   //----------------------------------------------------------------
   // 数据填充：添加单选项（文本+关联值）
   //----------------------------------------------------------------
   /**
    * @brief 向单选组添加一个单选项（文本+关联值）
    * @param item [in] 单选项显示文本（将显示在单选按钮标签上）
    * @param value [in] 单选项关联值（自定义业务值，需唯一，0 表示自动分配）
    * @return bool - 添加结果：true=成功（数据数组更新+视图刷新），false=失败
    * @note 1. 关联值重复时添加失败；
    *       2. 当项数超过可见数量时，自动启用垂直滚动条
    */
   virtual bool      AddItem(const string item,const long value=0);

   //----------------------------------------------------------------
   // 选中项值操作：获取/设置当前选中项的关联值
   //----------------------------------------------------------------
   /**
    * @brief 获取当前选中项的关联值
    * @return long - 选中项关联值（无选中项时返回 0）
    */
   long              Value(void) const;

   /**
    * @brief 通过关联值设置当前选中项
    * @param value [in] 目标关联值（需存在于 m_values 数组中）
    * @return bool - 设置结果：true=成功（找到对应项并选中），false=失败（值不存在）
    */
   bool              Value(const long value);

   /**
    * @brief 检查关联值是否已存在（避免重复添加）
    * @param value [in] 待检查的关联值
    * @return bool - 检查结果：true=值不存在（可添加），false=值已存在（不可添加）
    */
   bool              ValueCheck(long value) const;

   //----------------------------------------------------------------
   // 控件状态：设置单选组可见性
   //----------------------------------------------------------------
   /**
    * @brief 设置单选按钮组为可见状态
    * @return bool - 设置结果：true=成功（父类可见性设置+隐藏超出数据数量的单选按钮），false=失败
    */
   virtual bool      Show(void);

   //----------------------------------------------------------------
   // 状态序列化：保存/加载单选组状态到文件
   //----------------------------------------------------------------
   /**
    * @brief 将当前选中项的关联值保存到文件
    * @param file_handle [in] 文件句柄（需提前以 FILE_WRITE 模式打开）
    * @return bool - 保存结果：true=成功（关联值写入文件），false=失败（文件无效或写入错误）
    */
   virtual bool      Save(const int file_handle);

   /**
    * @brief 从文件加载选中项的关联值（恢复选中状态）
    * @param file_handle [in] 文件句柄（需提前以 FILE_READ 模式打开）
    * @return bool - 加载结果：true=成功（从文件读取值并设置选中项），false=失败
    */
   virtual bool      Load(const int file_handle);

protected:
   //----------------------------------------------------------------
   // 内部子控件创建：创建单个单选按钮（保护，仅内部调用）
   //----------------------------------------------------------------
   /**
    * @brief 创建指定索引的单选按钮（作为单选组的子项）
    * @param index [in] 单选按钮索引（对应 m_rows 数组下标）
    * @return bool - 创建结果：true=成功（单选按钮初始化+添加到容器+默认隐藏），false=失败
    */
   bool              CreateButton(const int index);

   //----------------------------------------------------------------
   // 滚动事件回调：处理滚动条显示/隐藏与滚动操作（保护，仅子类可重写）
   //----------------------------------------------------------------
   /**
    * @brief 垂直滚动条显示事件回调
    * @return bool - 处理结果：true=成功（调整单选按钮宽度以适配滚动条），false=失败
    */
   virtual bool      OnVScrollShow(void);

   /**
    * @brief 垂直滚动条隐藏事件回调
    * @return bool - 处理结果：true=成功（恢复单选按钮宽度），false=失败
    */
   virtual bool      OnVScrollHide(void);

   /**
    * @brief 向上滚动一行事件回调
    * @return bool - 处理结果：true=成功（更新滚动偏移量+视图刷新），false=失败
    */
   virtual bool      OnScrollLineUp(void);

   /**
    * @brief 向下滚动一行事件回调
    * @return bool - 处理结果：true=成功（更新滚动偏移量+视图刷新），false=失败
    */
   virtual bool      OnScrollLineDown(void);

   //----------------------------------------------------------------
   // 子项事件回调：处理单选按钮状态变更（保护，仅子类可重写）
   //----------------------------------------------------------------
   /**
    * @brief 单选项状态变更事件回调（子单选按钮被点击时触发）
    * @param row_index [in] 状态变更的子项索引（相对于可见区域，非数据数组索引）
    * @return bool - 处理结果：true=成功（更新选中项+取消其他项选中+发送 ON_CHANGE 事件），false=失败
    */
   virtual bool      OnChangeItem(const int row_index);

   //----------------------------------------------------------------
   // 视图刷新：更新单选组显示内容（保护，仅内部调用）
   //----------------------------------------------------------------
   /**
    * @brief 刷新单选组视图（同步数据数组与子单选按钮的文本和选中状态）
    * @return bool - 刷新结果：true=成功（所有可见单选按钮的文本和状态更新），false=失败
    */
   bool              Redraw(void);

   /**
    * @brief 设置指定单选按钮的选中状态（控制互斥逻辑）
    * @param index [in] 单选按钮索引（相对于可见区域，非数据数组索引）
    * @param select [in] 选中状态：true=选中，false=未选中
    * @return bool - 设置结果：true=成功（单选按钮状态更新），false=失败
    */
   bool              RowState(const int index,const bool select);

   /**
    * @brief 设置指定索引为当前选中项（内部调用，处理互斥逻辑）
    * @param index [in] 目标选中项索引（数据数组中的索引，CONTROLS_INVALID_INDEX 表示取消选中）
    */
   void              Select(const int index);
  };

//+------------------------------------------------------------------+
//| CRadioGroup 事件映射：关联子控件事件到处理函数（用于IDE智能提示）   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CRadioGroup)
   ON_INDEXED_EVENT(ON_CHANGE, m_rows, OnChangeItem)  // 单选项状态变更事件 → 调用 OnChangeItem（带项索引）
EVENT_MAP_END(CWndClient)
//+------------------------------------------------------------------+